﻿using DivaScript;
using mania4key_v2;
using MikuMikuModel.Logs;
using Newtonsoft.Json;
using ScriptUtilities;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace DivaScriptConv
{
    class Converter
    {
        public string _in;
        public string _ou;
        private Format _fm;
        public List<PvCommand> _pv;
        public int performers = 1;
        public bool to_aft = false;
        public bool from_json = false;
        public bool from_osu = false;
        public string PvName { get; set; }
        public string PvFieldData { get; set; }
        public bool IsBigEdian { get; set; }
        public bool CleanMultiTargets { get; set; }
        public bool CleanHoldTargets { get; set; }
        public bool CleanSlideTargets { get; set; }
        public bool CleanChanceTime { get; set; }
        public bool FixChanceTime { get; set; }
        public bool PvTargetsOnly { get; set; }
        public bool SingleLoad { get; set; }
        public string PvExpression { get; set; }
        public string[] OpcodeExcludeList { get; set; }

        public int offset = 0;
        public int pvid = -1;
        public Converter(string source, string dest, Format format)
        {
            _in = source;
            _ou = dest;
            _fm = format;
        }

        private void OpenFile()
        {
            if (from_osu)
            {
                OsuMap map = new OsuMap(_in);
                map.LoadNotes();
                var entryangle = 0;
                _pv = new List<PvCommand>();
                _pv.Insert(0, new PvCommand(0, "TARGET_FLYING_TIME", 700));
                //_pv.Insert(0, new PvCommand(0, "MUSIC_PLAY", new List<int>()));
                //_pv.Insert(0, new PvCommand(0, "TIME", 0));
                _pv.Insert(0, new PvCommand(1000 * 100, "MUSIC_PLAY", new List<int>()));
                foreach (var i in map.notes)
                {
                    entryangle = entryangle + 10;
                    if (entryangle >= 360) entryangle = 0;

                    int xcoord = i.keypress / 64;
                    int target = 0;
                    if (xcoord == 1)
                    {
                        xcoord = 0;
                        target = 0;
                    }
                    if (xcoord == 3)
                    {
                        xcoord = 1;
                        target = 3;
                    }
                    if (xcoord == 5)
                    {
                        xcoord = 2;
                        target = 2;
                    }
                    if (xcoord == 7)
                    {
                        xcoord = 3;
                        target = 1;
                    }

                    _pv.Add(new PvCommand(((int)i.tick + 1000 - 700) * 100, "TARGET", target, (25 + (30 * (xcoord + 1))) * 1000, 225 * 1000, 0, 300000, 0, 2));
                }
                _pv.Add(new PvCommand(_pv.Max(c => c.TimeStamp) + (1000 * 100), "PV_END", new List<int>()));
                _pv.Add(new PvCommand(_pv.Max(c => c.TimeStamp) + (1000 * 100), "END", new List<int>()));
                OrderByTimeStamp();
                CreateTime();
            }
            else
            if (from_json)
            {
                string file = File.ReadAllText(_in);
                _pv = JsonConvert.DeserializeObject<List<PvCommand>>(file);
            }
            else
            if (File.Exists(_in))
            {
                var i = 0;
                i = pvid;
                var fn = new FileInfo(_in).Name;
                if (fn.Length > 5 && fn.StartsWith("pv_") && Int32.TryParse(fn.Substring(3, 3), out i))
                {
                    PvName = fn.Substring(0, 6).ToLower();
                }
                var fl = new string[] { _in };
                _pv = (_fm == Format.DivaScript) ? PvScript.ParseTextFiles(fl) : PvScript.ParseBinaryScripts(fl, _fm, IsBigEdian);

                bool json = false;

                if (File.Exists("dbg"))
                {
                    json = true;
                }

                if (json)
                {
                    if (!Directory.Exists("dsc_json"))
                    {
                        Directory.CreateDirectory("dsc_json\\");
                    }

                    JsonSerializer serializer = new JsonSerializer();
                    serializer.NullValueHandling = NullValueHandling.Ignore;

                    using (StreamWriter sw = new StreamWriter("dsc_json\\" + Path.GetFileNameWithoutExtension(_in) + "_before.txt"))
                    using (JsonWriter writer = new JsonTextWriter(sw))
                    {
                        serializer.Serialize(writer, _pv);
                    }
                }

            }
        }

        private List<PvCommand> OpenFile(string file)
        {
            var fl = new string[] { file };
            var _apv = (_fm == Format.DivaScript) ? PvScript.ParseTextFiles(fl) : PvScript.ParseBinaryScripts(fl, _fm, IsBigEdian);
            return _apv;
        }

        private void OpenAndMergeScene(string file)
        {
            pvid = int.Parse(Path.GetFileNameWithoutExtension(_in).Substring(3, 3));

            String[] str;
            if (File.Exists("pv_" + this.pvid))
                str = File.ReadAllLines("x_" + this.pvid + ".csv");
            else str = File.ReadAllLines("x_generic.csv");

            var i = 0;
            var fn = new FileInfo(_in).Name;
            if (fn.Length > 5 && fn.StartsWith("pv_") && Int32.TryParse(fn.Substring(3, 3), out i))
            {
                PvName = fn.Substring(0, 6).ToLower();
            }
            var fl = new string[] { file };
            var _apv = (_fm == Format.DivaScript) ? PvScript.ParseTextFiles(fl) : PvScript.ParseBinaryScripts(fl, _fm, IsBigEdian);

            foreach (var c in _apv.ToList())
            {
                if (c.Opcode == "CHANGE_FIELD")
                {
                    bool found = false;
                    int type = 0;
                    foreach (var cc in str)
                    {
                        var lines = cc.Split(',');
                        var fieldid = int.Parse(lines[0]);
                        var fieldtype = int.Parse(lines[1]);
                        var param = c.Parameters[0];

                        if (param > fieldid)
                            type = fieldtype;
                        if (param == fieldid)
                        {
                            found = true;
                            _pv.Add(new PvCommand(c.TimeStamp, "CHANGE_FIELD", fieldtype));
                            break;
                        }
                    }

                    if (!found)
                        _pv.Add(new PvCommand(c.TimeStamp, "CHANGE_FIELD", type));
                }
            }



        }
        private void OpenAndMerge(string file)
        {
            var i = 0;
            var fn = new FileInfo(_in).Name;
            if (fn.Length > 5 && fn.StartsWith("pv_") && Int32.TryParse(fn.Substring(3, 3), out i))
            {
                PvName = fn.Substring(0, 6).ToLower();
            }
            var fl = new string[] { file };
            var _apv = (_fm == Format.DivaScript) ? PvScript.ParseTextFiles(fl) : PvScript.ParseBinaryScripts(fl, _fm, IsBigEdian);

            foreach (var c in _apv.ToList())
            {
                if (c.Opcode == "TIME")
                    _apv.Remove(c);
                if (c.Opcode == "END")
                    _apv.Remove(c);
            }

            foreach (var c in _apv)
            {
                _pv.Add(c);
            }

            bool json = false;

            if (File.Exists("dbg"))
            {
                json = true;
            }

            if (json)
            {
                if (!Directory.Exists("dsc_json"))
                {
                    Directory.CreateDirectory("dsc_json\\");
                }

                JsonSerializer serializer = new JsonSerializer();
                serializer.NullValueHandling = NullValueHandling.Ignore;

                using (StreamWriter sw = new StreamWriter("dsc_json\\" + Path.GetFileNameWithoutExtension(_in) + "_before.txt"))
                using (JsonWriter writer = new JsonTextWriter(sw))
                {
                    serializer.Serialize(writer, _pv);
                }
            }
        }

        private void SaveFile(Format format)
        {
            if (format == Format.DivaScript)
            {
                PvScript.WriteTextScript(_ou, _pv);
            }
            else
            {
                if (from_osu)
                    PvScript.WriteTextScript(_ou + ".txt", _pv);
                PvScript.WriteBinaryScript(_ou, _pv, format, IsBigEdian);
            }
        }

        private void PostProcessA()
        {
#if (!PUBLIC)
            PvFieldFactory pvf = null;
            // Load pv_field data first
            if (!String.IsNullOrEmpty(PvFieldData) && File.Exists(PvFieldData))
            {
                pvf = new PvFieldFactory(PvFieldData);
            }
            // Update and remove unfits
            PvCommand last = null;
            using (List<PvCommand>.Enumerator e1 = _pv.ToList<PvCommand>().GetEnumerator())
            {
                var indx = 0;
                var cfld = 0;
                var flag = false;
                var slid = false;
                var bran = false;
                var lcam = String.Empty;
                var fstg = new bool[] { false, false };
                var bstg = new bool[] { false, false };
                var lstg = new string[] { String.Empty, String.Empty };
                var lmot = new string[] { String.Empty, String.Empty };
                var ldis = new string[] { String.Empty, String.Empty };
                while (e1.MoveNext())
                {
                    indx++;
                    PvCommand cur = e1.Current;
                    if (PvTargetsOnly)
                    {
                        if (cur.Opcode == "TIME" && cur.Parameters[0] == 0)
                            _pv.Insert(indx, new PvCommand(0, "MUSIC_PLAY"));
                        if (cur.Opcode == "MUSIC_PLAY" || cur.Opcode == "MOVIE_PLAY" || cur.Opcode == "MOVIE_DISP")
                            flag = true;
                    }
                    if (cur.Opcode == "TIME" || cur.Opcode == "PV_BRANCH_MODE")
                    {
                        bran = false;
                    }
                    if (bran)
                    {
                        flag = true;
                    }
                    else if (cur.Opcode == "MODE_SELECT")
                    {
                        if (CleanChanceTime)
                            flag = true;
                        // else if (FixChanceTime)
                        // TODO: FixChanceTime / Not supported yet
                    }
                    else if (cur.Opcode == "EXPRESSION")
                    {
                        if (cur.Parameters[0] > 1) flag = true;
                        if (!String.IsNullOrEmpty(PvExpression) && cur.Parameters[1] == 23) cur.Parameters[1] = 11;
                    }
                    else if (cur.Opcode == "PV_BRANCH_MODE")
                    {
                        if (cur.Parameters[0] == 1)
                            bran = true;
                        else
                            bran = false;
                    }
                    else if (cur.Opcode == "CHANGE_FIELD")
                    {
                        // cfld = cur.Parameters[0];
                        // if (pvf != null)
                        // {
                        //     var cstg = pvf.GetStageName(PvName, cfld);
                        //     for (int i = 0; i < 2; i++) if (bstg[i] && !cstg.Equals(lstg[i])) lstg[i] = cstg;
                        // }
                    }
                    else if (cur.Opcode == "DATA_CAMERA")
                    {
                        /*
                        var ccam = cur.ToString();
                        if (ccam.Equals(lcam))
                            flag = true;
                        else
                            lcam = ccam;
                        */
                    }
                    else if (cur.Opcode == "MIKU_DISP")
                    {
                        /*)
                        var ci = cur.Parameters[0];
                        if (ci > 1)
                            flag = true;
                        else
                        {
                            if (fstg[ci] == false)
                            {
                                cur.Parameters[1] = 1;
                                fstg[ci] = true;
                            }
                            else if (SingleLoad) flag = true;
                            var cdis = cur.ToString();
                            if (pvf != null)
                            {
                                var cstg = pvf.GetStageName(PvName, cfld);
                                if (bstg[ci] && cstg.Equals(lstg[ci]))
                                    flag = true;
                                else
                                {
                                    bstg[ci] = (cur.Parameters[1] == 1);
                                    lstg[ci] = cstg;
                                    ldis[ci] = cdis;
                                }
                            }
                            else if (cdis.Equals(ldis[ci]))
                                flag = true;
                            else ldis[ci] = cdis;
                        }
                        */
                    }
                    else if (cur.Opcode == "SET_MOTION")
                    {
                        /*
                        var ci = cur.Parameters[0];
                        if (ci > 1)
                            flag = true;
                        else
                        {
                            var cmot = cur.ToString();
                            if (cmot.Equals(lmot[ci]))
                                flag = true;
                            else
                                lmot[ci] = cmot;
                        }
                        */
                    }
                    else if (cur.Opcode == "TARGET")
                    {
                        if (CleanMultiTargets && last != null && last.Opcode == "TARGET")
                        {
                            flag = true;
                        }
                        else
                        {
                            var t = (FtTargetType)cur.Parameters[0];
                            if (slid && t != FtTargetType.slide_chain_l && t != FtTargetType.slide_chain_r)
                                slid = false;
                            if (!CleanSlideTargets)
                            {
                                switch (t)
                                {
                                    case FtTargetType.slide_chain_l:
                                        t = FtTargetType.shikaku_hold;
                                        if (slid) flag = true;
                                        else slid = true;
                                        break;
                                    case FtTargetType.slide_chain_r:
                                        t = FtTargetType.maru_hold;
                                        if (slid) flag = true;
                                        else slid = true;
                                        break;
                                    case FtTargetType.slide_l:
                                        t = FtTargetType.shikaku;
                                        break;
                                    case FtTargetType.slide_r:
                                        t = FtTargetType.maru;
                                        break;
                                    case FtTargetType.slide_l_ch:
                                        t = FtTargetType.shikaku_ch;
                                        break;
                                    case FtTargetType.slide_r_ch:
                                        t = FtTargetType.maru_ch;
                                        break;
                                    default:
                                        break;
                                }
                            }
                            if (CleanChanceTime)
                            {
                                switch (t)
                                {
                                    case FtTargetType.batsu_ch:
                                        t = FtTargetType.batsu;
                                        break;
                                    case FtTargetType.maru_ch:
                                        t = FtTargetType.maru;
                                        break;
                                    case FtTargetType.sankaku_ch:
                                        t = FtTargetType.sankaku;
                                        break;
                                    case FtTargetType.shikaku_ch:
                                        t = FtTargetType.shikaku;
                                        break;
                                    default:
                                        break;
                                }
                            }
                            if (CleanHoldTargets)
                            {
                                switch (t)
                                {
                                    case FtTargetType.random_hold:
                                        t = FtTargetType.random;
                                        break;
                                    case FtTargetType.batsu_hold:
                                        t = FtTargetType.batsu;
                                        break;
                                    case FtTargetType.maru_hold:
                                        t = FtTargetType.maru;
                                        break;
                                    case FtTargetType.sankaku_hold:
                                        t = FtTargetType.sankaku;
                                        break;
                                    case FtTargetType.shikaku_hold:
                                        t = FtTargetType.shikaku;
                                        break;
                                    default:
                                        break;
                                }
                            }
                            var s = (int)t;
                            if (s > 11) flag = true;
                            else cur.Parameters[0] = s;
                        }
                    }
                    else if (PvTargetsOnly)
                    {
                        if (cur.Opcode == "CHANGE_FIELD") flag = (cur.Parameters[0] > 1);
                        //else if (cur.Opcode == "END")
                        //    _pv.Insert(indx - 1, new PvCommand(cur.TimeStamp, "PV_END"));
                        else if (!cur.Opcode.Contains("TARGET")
                            && cur.Opcode != "TIME" && cur.Opcode != "LYRIC"
                            && cur.Opcode != "PV_END" && cur.Opcode != "END")
                            flag = true;
                    }
                    if (cur.Opcode == "EDIT_CAMERA")
                    {
                        cur.Parameters.Remove(cur.Parameters[19]);
                        cur.Parameters.Remove(cur.Parameters[20]);
                        cur.Parameters.Remove(cur.Parameters[21]);
                        cur.Opcode = "MOVE_CAMERA";
                    }

                    if ((cur.Opcode.Contains("OSAGE_")))
                    {
                        flag = true;
                    }

                    if ((cur.Opcode.Contains("EDIT_EYELID_ANIM")))
                    {
                        flag = true;
                    }

                    if ((cur.Opcode.Contains("EDIT_EYE_ANIM")))
                    {
                        flag = true;
                    }

                    if ((cur.Opcode.Contains("MOT_SMOOTH")))
                    {
                        flag = true;
                    }

                    if ((cur.Opcode.Contains("EDIT_INSTRUMENT_ITEM")))
                    {
                        flag = true;
                    }

                    if (cur.Opcode == "EDIT_MOVE_XYZ")
                    {
                        cur.Parameters.Remove(cur.Parameters[2]); //2 or 3??
                        cur.Parameters.Remove(cur.Parameters[5]); //5 or 6??
                        cur.Opcode = "EDIT_MOVE";
                    }

                    if ((Storage.PdaNop.Contains(cur.Opcode)) || (OpcodeExcludeList != null && OpcodeExcludeList.Contains(cur.Opcode)))
                    {
                        flag = true;
                    }
                    if ((cur.Opcode.Contains("WIND")))
                    {
                        if (cur.Parameters[0] == 0)
                            if (cur.Parameters[1] == 0)
                                if (cur.Parameters[2] == 0)
                                    flag = true;

                        if (cur.Parameters[0] == 1)
                            if (cur.Parameters[1] == 0)
                                if (cur.Parameters[2] == 1000)
                                    flag = true;

                        if (cur.Parameters[0] == 1)
                            if (cur.Parameters[1] == 0)
                                if (cur.Parameters[2] == 0)
                                    flag = true;

                        if (this._in.Contains("601"))
                        {
                            flag = true;
                        }

                        if (this._in.Contains("629"))
                        {
                            flag = true;
                        }

                        if (this._in.Contains("618"))
                        {
                            flag = true;
                        }
                    }
                    if (!to_aft)
                        if ((cur.Opcode.Contains("MIKU_") || cur.Opcode.Contains("SET_") || cur.Opcode.Contains("_ANIM") || cur.Opcode.Contains("CHARA_SIZE")) && cur.Parameters[0] > 1)
                        {
                            if (cur.Parameters[0] > 1)
                                flag = true;
                        }
                    if (_fm == Format.X)
                    {
                        if (indx == 1)
                        {
                            //_pv.Insert(0, new PvCommand(0, "CHANGE_FIELD", 1));
                            var mikudispint = new int[2];
                            mikudispint[0] = 0; mikudispint[1] = 1;
                            var tyme = new int[1];
                            tyme[0] = 0;
                            var field = new int[1];
                            field[0] = 1;
                            var miku_move = new int[4];
                            miku_move[0] = 0;
                            miku_move[1] = 0;
                            miku_move[2] = 0;
                            miku_move[3] = 0;
                            var miku_rot = new int[2];
                            miku_rot[0] = 0;
                            miku_rot[1] = 0;

                            var moti = new int[4];
                            moti[0] = 0; moti[1] = 1;
                            moti[2] = -1; moti[3] = 1000;

                            var pfield = new int[2];
                            pfield[0] = 1; pfield[1] = 1;

                            var moti2 = new int[4];
                            moti2[0] = 1; moti2[1] = 1;
                            moti2[2] = -1; moti2[3] = 1000;

                            var moti3 = new int[4];
                            moti2[0] = 2; moti2[1] = 1;
                            moti2[2] = -1; moti2[3] = 1000;

                            //_pv.Insert(0, new PvCommand(0, "CHANGE_FIELD", 0));

                            if (performers == 2)
                            {
                                _pv.Insert(0, new PvCommand(0, "HAND_ANIM", 1, 0, 9, -1, -1));
                                _pv.Insert(0, new PvCommand(0, "HAND_ANIM", 1, 1, 9, -1, -1));
                            }

                            if (performers == 3)
                            {

                                _pv.Insert(0, new PvCommand(0, "HAND_ANIM", 2, 0, 9, -1, -1));
                                _pv.Insert(0, new PvCommand(0, "HAND_ANIM", 2, 1, 9, -1, -1));

                                _pv.Insert(0, new PvCommand(0, "HAND_ANIM", 1, 0, 9, -1, -1));
                                _pv.Insert(0, new PvCommand(0, "HAND_ANIM", 1, 1, 9, -1, -1));

                            }

                            _pv.Insert(0, new PvCommand(0, "HAND_ANIM", 0, 0, 9, -1, -1));
                            _pv.Insert(0, new PvCommand(0, "HAND_ANIM", 0, 1, 9, -1, -1));

                            if (performers == 3)
                            {
                                _pv.Insert(0, new PvCommand(0, "SET_MOTION", 2, 1, -1, 1000));
                                _pv.Insert(0, new PvCommand(0, "SET_MOTION", 1, 1, -1, 1000));
                            }

                            if (performers == 2)
                            {
                                _pv.Insert(0, new PvCommand(0, "SET_MOTION", 1, 1, -1, 1000));
                            }

                            _pv.Insert(0, new PvCommand(0, "SET_MOTION", 0, 1, -1, 1000));

                            _pv.Insert(0, new PvCommand(0, "SET_PLAYDATA", 0, 1));

                            if (performers == 2)
                            {
                                _pv.Insert(0, new PvCommand(0, "MIKU_ROT", 1, 0));
                                _pv.Insert(0, new PvCommand(0, "MIKU_MOVE", 1, 0, 0, 0));
                                _pv.Insert(0, new PvCommand(0, "MIKU_DISP", 1, 1));
                            }

                            if (performers == 3)
                            {
                                _pv.Insert(0, new PvCommand(0, "MIKU_ROT", 2, 0));
                                _pv.Insert(0, new PvCommand(0, "MIKU_MOVE", 2, 0, 0, 0));
                                _pv.Insert(0, new PvCommand(0, "MIKU_DISP", 2, 1));

                                _pv.Insert(0, new PvCommand(0, "MIKU_ROT", 1, 0));
                                _pv.Insert(0, new PvCommand(0, "MIKU_MOVE", 1, 0, 0, 0));
                                _pv.Insert(0, new PvCommand(0, "MIKU_DISP", 1, 1));
                            }

                            _pv.Insert(0, new PvCommand(0, "MIKU_ROT", miku_move));
                            _pv.Insert(0, new PvCommand(0, "MIKU_MOVE", miku_move));
                            _pv.Insert(0, new PvCommand(0, "MIKU_DISP", mikudispint));
                            _pv.Insert(0, new PvCommand(0, "DATA_CAMERA", mikudispint));
                            //_pv.Insert(0, new PvCommand(0, "DATA_CAMERA_START", mikudispint));
                            //_pv.Insert(0, new PvCommand(0, "CHANGE_FIELD", pfield));
                            //_pv.Insert(0, new PvCommand(0, "FACE_TYPE", tyme));
                            _pv.Insert(0, new PvCommand(0, "MUSIC_PLAY", new List<int>()));
                            _pv.Insert(0, new PvCommand(0, "TIME", tyme.ToList()));
                            //_pv.Insert(0, new PvCommand(0, "PV_BRANCH_MODE", tyme));

                        }


                        /*
                        if (cur.Opcode == "TIME")
                        {
                            if (cur.Parameters[0] != 0)
                            cur.Parameters[0] = cur.Parameters[0] + 35000;
                            cur.TimeStamp = cur.TimeStamp + 35000;
                        }
                        */

                        //cur.TimeStamp = cur.TimeStamp + offset;

                        if (cur.Opcode == "END")
                        {

                        }


                        if (cur.Opcode == "TARGET")
                        {
                            /*
                            var newcommand = new int[7];
                            newcommand[0] = cur.Parameters[0];
                            newcommand[1] = cur.Parameters[3];
                            newcommand[2] = cur.Parameters[4];
                            newcommand[3] = cur.Parameters[5];
                            newcommand[4] = cur.Parameters[7];
                            newcommand[5] = cur.Parameters[8];
                            newcommand[6] = cur.Parameters[11];
                            cur.Parameters = newcommand.ToList();
                            */

                            /*
                            if (indx - 1 > 0)
                            {
                                PvCommand next = this._pv[indx - 1];
                                if (next.Opcode == "TIME")
                                {
                                    next.Parameters[0] = next.Parameters[0] + 125000;
                                    Console.WriteLine("chengd shite " + next.Parameters[0]);
                                }
                            }
                            */

                        }

                    }
                    if (flag) _pv.Remove(cur);
                    last = cur; flag = false;
                }
            }

            // Remove duplicates
            last = null;
            var pv = new PvCommand[_pv.Count];
            var o = 0; _pv.CopyTo(pv);
            for (int i = 0; i < pv.Length; i++)
            {
                PvCommand cur = pv[i];
                if (last == null)
                {
                    last = cur;
                    continue;
                }
                if (cur.ToString().Equals(last.ToString()))
                {
                    _pv.RemoveAt(i - o);
                    o++;
                }
                else if (cur.Opcode == "TIME" && last.Opcode == "TIME")
                {
                    _pv.RemoveAt(i - o - 1);
                    o++;
                }
                last = cur;
            }
#endif
        }

        private void PostProcessF()
        {
            PvCommand last = null;
            // Update and remove unfits
            using (List<PvCommand>.Enumerator e1 = _pv.ToList<PvCommand>().GetEnumerator())
            {
                var indx = 0;
                var flag = false;
                var chance = false;
                PvCommand lastStar = null;
                PvCommand lastMulti = null;
                PvCommand lastTarget = null;
                while (e1.MoveNext())
                {
                    indx++;
                    PvCommand cur = e1.Current;
                    if (PvTargetsOnly)
                    {
                        if (cur.Opcode == "TIME" && cur.Parameters[0] == 0)
                        {
                            _pv.Insert(indx, new PvCommand(0, "MOVIE_DISP", 1));
                            _pv.Insert(indx, new PvCommand(0, "MOVIE_PLAY", 1));
                            _pv.Insert(indx, new PvCommand(0, "MUSIC_PLAY"));
                        }
                        if (cur.Opcode == "MUSIC_PLAY" || cur.Opcode == "PV_BRANCH_MODE" ||
                            cur.Opcode == "MOVIE_PLAY" || cur.Opcode == "MOVIE_DISP")
                            flag = true;
                    }
                    if (cur.Opcode == "MODE_SELECT")
                    {
                        if (CleanChanceTime)
                            flag = true;
                        else if (FixChanceTime)
                        {
                            if (cur.Parameters[1] == 1)
                                chance = true;
                            else if (cur.Parameters[1] == 3)
                                if (lastStar != null) lastStar.Parameters[0] = 15;
                                else if (lastMulti != null) lastMulti.Parameters[0] = 15;
                                else if (lastTarget != null) lastTarget.Parameters[0] = 15;
                        }
                    }
                    else if (cur.Opcode == "TARGET")
                    {
                        if (last != null && last.Opcode == "TARGET")
                        {
                            if (chance) lastMulti = lastTarget;
                            flag = true;
                        }
                        else if (chance && cur.Parameters[0] == (int)F2TargetType.touch)
                            lastStar = cur;
                        else
                            lastTarget = cur;
                    }
                    else if (PvTargetsOnly)
                    {
                        if (cur.Opcode == "CHANGE_FIELD") flag = (cur.Parameters[0] > 1);
                        else if (cur.Opcode == "END")
                            _pv.Insert(indx - 1, new PvCommand(cur.TimeStamp, "PV_END"));
                        else if (!cur.Opcode.Contains("TARGET")
                            && cur.Opcode != "TIME" && cur.Opcode != "LYRIC"
                            && cur.Opcode != "PV_END" && cur.Opcode != "END")
                            flag = true;
                    }
                    else if (OpcodeExcludeList != null && OpcodeExcludeList.Contains(cur.Opcode))
                    {
                        flag = true;
                    }
                    if (flag) _pv.Remove(cur);
                    last = cur; flag = false;
                }
            }

            // Remove duplicates
            last = null;
            var pv = new PvCommand[_pv.Count];
            var o = 0; _pv.CopyTo(pv);
            for (int i = 0; i < pv.Length; i++)
            {
                PvCommand cur = pv[i];
                if (last == null)
                {
                    last = cur;
                    continue;
                }
                if (cur.ToString().Equals(last.ToString()))
                {
                    _pv.RemoveAt(i - o);
                    o++;
                }
                else if (cur.Opcode == "TIME" && last.Opcode == "TIME")
                {
                    _pv.RemoveAt(i - o - 1);
                    o++;
                }
                last = cur;
            }
        }

        private void PostProcessFt()
        {
            PvCommand last = null;
            using (List<PvCommand>.Enumerator e1 = _pv.ToList<PvCommand>().GetEnumerator())
            {
                var indx = 0;
                var flag = false;
                // var bran = false;
                while (e1.MoveNext())
                {
                    indx++;
                    PvCommand cur = e1.Current;
                    if (PvTargetsOnly)
                    {
                        if (cur.Opcode == "TIME" && cur.Parameters[0] == 0)
                        {
                            _pv.Insert(indx, new PvCommand(0, "MOVIE_DISP", 1));
                            _pv.Insert(indx, new PvCommand(0, "MOVIE_PLAY", 1));
                            _pv.Insert(indx, new PvCommand(0, "MUSIC_PLAY"));
                        }
                        if (cur.Opcode == "MUSIC_PLAY" || cur.Opcode == "MOVIE_PLAY" || cur.Opcode == "MOVIE_DISP")
                            flag = true;
                    }
                    // if (cur.Opcode == "TIME" || cur.Opcode == "PV_BRANCH_MODE")
                    // {
                    //     bran = false;
                    // }
                    // if (bran)
                    // {
                    //     flag = true;
                    // }
                    if (cur.Opcode == "MODE_SELECT")
                    {
                        if (CleanChanceTime)
                            flag = true;
                        // else if (FixChanceTime)
                        // TODO: FixChanceTime / Not supported yet
                    }
                    else if (cur.Opcode == "EXPRESSION")
                    {
                        if (cur.Parameters[0] > 1) flag = true;
                        if (!String.IsNullOrEmpty(PvExpression) && cur.Parameters[1] == 23) cur.Parameters[1] = 11;
                    }
                    // else if (cur.Opcode == "PV_BRANCH_MODE")
                    // {
                    //     if (cur.Parameters[0] == 1)
                    //         bran = true;
                    //     else
                    //         bran = false;
                    // }
                    else if (cur.Opcode == "TARGET")
                    {
                        if (CleanMultiTargets && last != null && last.Opcode == "TARGET")
                        {
                            flag = true;
                        }
                    }
                    else if (PvTargetsOnly)
                    {
                        if (cur.Opcode == "CHANGE_FIELD") flag = (cur.Parameters[0] > 1);
                        else if (cur.Opcode == "END")
                            _pv.Insert(indx - 1, new PvCommand(cur.TimeStamp, "PV_END"));
                        else if (!cur.Opcode.Contains("TARGET")
                            && cur.Opcode != "TIME" && cur.Opcode != "LYRIC"
                            && cur.Opcode != "PV_END" && cur.Opcode != "END")
                            flag = true;
                    }
                    if (OpcodeExcludeList != null && OpcodeExcludeList.Contains(cur.Opcode))
                    {
                        flag = true;
                    }
                    if (flag) _pv.Remove(cur);
                    last = cur; flag = false;
                }
            }

            // Remove duplicates
            last = null;
            var pv = new PvCommand[_pv.Count];
            var o = 0; _pv.CopyTo(pv);
            for (int i = 0; i < pv.Length; i++)
            {
                PvCommand cur = pv[i];
                if (last == null)
                {
                    last = cur;
                    continue;
                }
                if (cur.ToString().Equals(last.ToString()))
                {
                    _pv.RemoveAt(i - o);
                    o++;
                }
                else if (cur.Opcode == "TIME" && last.Opcode == "TIME")
                {
                    _pv.RemoveAt(i - o - 1);
                    o++;
                }
                last = cur;
            }
        }

        private void SyncExpression2()
        {
            if (_pv == null) return; // Cannot run when file is not opened yet
            if (String.IsNullOrEmpty(PvExpression) || !File.Exists(PvExpression)) return;
            var dex = new DexFactory(PvExpression);
            if (dex.ReadDEX() != 1) return; // Error when reading DEX
            if (dex.DEX.Length == 0) return; // Has no DEX data
            int lp, ll;
            // TODO : SyncExpression / Not supported yet
            lp = 0; ll = _pv.Count;

            for (int d = 0; d < dex.DEX.Length; d++)
            {
                foreach (var e in dex.DEX[d].Main)
                {
                    var te = (int)Math.Round(e.Frame * 100000 / 60, 0);
                    var h = (d > 0 && !dex.DEX[d].Name.Contains("P1")) ? 1 : 0;
                    var ea = new PvCommand(te, "EXPRESSION", h, e.ID, (int)Math.Round((e.Value * 1000), 0), -1);

                    var index = _pv.Where(c => c.Opcode == "TIME").Where(k => k.Parameters[0] > te).FirstOrDefault();
                    if (index != null)
                    {
                        var indx = _pv.IndexOf(index);
                        if (index.Parameters[0] != te) _pv.Insert(indx, new PvCommand(te, "TIME", te));
                        indx = _pv.IndexOf(index);
                        _pv.Insert(indx, ea);
                    }

                }

                foreach (var e in dex.DEX[d].Eyes)
                {
                    var te = (int)Math.Round(e.Frame * 100000 / 60, 0);
                    var h = (d > 0 && !dex.DEX[d].Name.Contains("P1")) ? 1 : 0;
                    var ea = new PvCommand(te, "EYE_ANIM", h, 2 - (int)Math.Round(e.Value, 0), -1);

                    var index = _pv.Where(c => c.Opcode == "TIME").Where(k => k.Parameters[0] > te).FirstOrDefault();
                    if (index != null)
                    {
                        var indx = _pv.IndexOf(index);
                        if (index.Parameters[0] != te) _pv.Insert(indx, new PvCommand(te, "TIME", te));
                        indx = _pv.IndexOf(index);
                        _pv.Insert(indx, ea);
                    }
                }
            }
        }

        private void SyncExpression(int last = int.MaxValue)
        {
            if (_pv == null) return; // Cannot run when file is not opened yet
            if (String.IsNullOrEmpty(PvExpression) || !File.Exists(PvExpression)) return;
            var dex = new DexFactory(PvExpression);
            if (dex.ReadDEX() != 1) return; // Error when reading DEX
            if (dex.DEX.Length == 0) return; // Has no DEX data
            int lp, ll;
            // TODO : SyncExpression / Not supported yet
            lp = 0; ll = _pv.Count;


            for (int d = 0; d < dex.DEX.Length; d++)
            {
                foreach (var e in dex.DEX[d].Main)
                {
                    var te = (int)Math.Round(e.Frame * 100000 / 60, 0);
                    var h = (d > 0 && !dex.DEX[d].Name.Contains("P1")) ? 1 : 0;
                    var ea = new PvCommand(te, "EXPRESSION", h, e.ID, (int)Math.Round((e.Value * 1000), 0), -1);
                    if (te < last)
                        _pv.Add(ea);
                    else
                    {
                        Logs.WriteLine("DSC : Skipped 1 exp at " + te);
                    }
                }

                foreach (var e in dex.DEX[d].Eyes)
                {
                    var te = (int)Math.Round(e.Frame * 100000 / 60, 0);
                    var h = (d > 0 && !dex.DEX[d].Name.Contains("P1")) ? 1 : 0;
                    var ea = new PvCommand(te, "EYE_ANIM", h, 2 - (int)Math.Round(e.Value, 0), -1);
                    if (te < last)
                        _pv.Add(ea);
                    else
                    {
                        Logs.WriteLine("DSC : Skipped 1 exp at " + te);
                    }
                }
            }
        }

        private void StripTime()
        {
            foreach (var i in _pv.ToList())
            {
                if (i.Opcode == "TIME")
                    _pv.Remove(i);
            }
        }

        private void CreateTime()
        {
            var lasttimestamp = -1;
            int index = 0;
            List<PvCommand> new_pv = new List<PvCommand>();

            foreach (var i in _pv)
            {
                if (i.TimeStamp != lasttimestamp)
                {
                    if (i.TimeStamp != 0)
                        new_pv.Add(new PvCommand(i.TimeStamp + offset, "TIME", i.TimeStamp + offset));
                    else new_pv.Add(new PvCommand(0, "TIME", 0));
                    new_pv.Add(i);
                }
                else
                {
                    new_pv.Add(i);
                }
            }

            _pv.Clear();
            foreach (var i in new_pv)
            {
                _pv.Add(i);
            }
        }

        private void OrderByTimeStamp()
        {
            _pv = _pv.OrderBy(c => c.TimeStamp).ToList();
        }

        public void ConvertToTxt()
        {
            OpenFile();
            SaveFile(Format.DivaScript);
        }

        private void ConvertFtToA()
        {
            OpenFile();
            //StripTime();
            if (!String.IsNullOrEmpty(PvExpression)) SyncExpression2();
            if (!from_osu) PostProcessA();
            //StripTime();
            //OrderByTimeStamp();
            //CreateTime();
            SaveFile(Format.FT);
        }

        private void FilterNull()
        {
            foreach (var i in _pv.ToList())
            {
                //if (i.Parameters.Count() == 0)
                //    _pv.Remove(i);
            }
        }

        private void RemoveOpcode(string opcode)
        {
            foreach (var i in _pv.ToList())
            {
                if (i.Opcode == opcode)
                    _pv.Remove(i);
            }
        }

        private void Offset(string opcode, int timeoffset)
        {
            foreach (var i in _pv.ToList())
            {
                if (i.Opcode == opcode)
                    i.TimeStamp = i.TimeStamp + timeoffset;
            }
        }
        const bool xmode = true;
        private void ConvertXToA()
        {
            OpenFile();

            CommandFormatting.SmartReformatFtoFt(_pv);

            pvid = int.Parse(Path.GetFileNameWithoutExtension(_in).Substring(3, 3));

            if (xmode)
            {
                var pvcmds = new PvCmds();
                pvcmds.Load(_pv);

                if (File.Exists("dsc\\common\\pv_" + pvid + "_scene.dsc"))
                {
                    var scene = OpenFile("dsc\\common\\pv_" + String.Format("{0:000}", pvid) + "_scene.dsc");
                    scene.RemoveAll(c => c.Opcode != "CHANGE_FIELD");
                }

                if (File.Exists("dsc\\common\\pv_" + pvid + "_mouth.dsc"))
                    pvcmds.Load(OpenFile("dsc\\common\\pv_" + String.Format("{0:000}", pvid) + "_mouth.dsc"));

                String[] str;
                if (File.Exists("pv_" + this.pvid))
                    str = File.ReadAllLines("x_" + this.pvid + ".csv");
                else str = File.ReadAllLines("x_generic.csv");

                pvcmds.RemoveOpcode("SET_STAGE_EFFECT_ENV");
                pvcmds.RemoveOpcode("LYRICS");

                _pv.Clear();
                Logs.WriteLine("DSC : Last is " + pvcmds.pvCommands.OrderBy(c => c.Time).Last().Time);
                if (!String.IsNullOrEmpty(PvExpression)) SyncExpression(pvcmds.pvCommands.OrderBy(c => c.Time).Last().Time);
                pvcmds.Load(_pv);

                pvcmds.Insert(new PvCommand(0, "MUSIC_PLAY"), 0, 0);

                foreach (var cmd in pvcmds.pvCommands.ToList())
                {
                    var _pv = cmd.pvCommands;
                    var co = _pv.Where(c => c.Opcode == "DATA_CAMERA");
                    foreach (var k in co.ToList())
                    {
                        int index = _pv.IndexOf(k);
                        var mikudispint = new int[2];
                        mikudispint[0] = 0; mikudispint[1] = 1;
                    }
                    co = _pv.Where(c => c.Opcode == "MOUTH_ANIM");
                    foreach (var k in co.ToList())
                    {
                        if (k.Parameters[2] == 14)
                        {
                            k.Parameters[2] = 28;
                            k.Parameters[3] = 100;
                            k.Parameters[4] = 1000;
                        }
                    }
                    foreach (var c in _pv.ToList())
                    {
                        if (c.Opcode == "CHANGE_FIELD")
                        {
                            bool found = false;
                            int type = 0;
                            foreach (var cc in str)
                            {
                                var lines = cc.Split(',');
                                var fieldid = int.Parse(lines[0]);
                                var fieldtype = int.Parse(lines[1]);
                                var param = c.Parameters[0];

                                if (param > fieldid)
                                    type = fieldtype;
                                if (param == fieldid)
                                {
                                    found = true;
                                    if (c.TimeStamp == 0) c.TimeStamp = 1;
                                    pvcmds.Insert(new PvCommand(c.TimeStamp, "CHANGE_FIELD", fieldtype), c.TimeStamp);
                                    break;
                                }
                            }

                            if (!found)
                            {
                                if (c.TimeStamp == 0) c.TimeStamp = 1;
                                pvcmds.Insert(new PvCommand(c.TimeStamp, "CHANGE_FIELD", type), c.TimeStamp);
                            }
                        }
                    }
                    co = _pv.Where(c => c.Opcode == "CHANGE_FIELD");
                    foreach (var k in co.ToList())
                    {
                        if (k.Parameters.Count == 2)
                            _pv.Remove(k);
                    }
                }

                _pv.Clear();
                _pv = pvcmds.Export();
                pvcmds.RemoveOpcode("END");
                pvcmds.RemoveOpcode("PV_END");
                var dc = _pv.Where(c => c.Opcode == "END").LastOrDefault();
                pvcmds.Insert(dc, dc.TimeStamp);

                if (dc != null)
                {
                    var cd = _pv.Where(c => c.Opcode == "PV_END");
                    foreach (var i in cd)
                    {
                        pvcmds.Insert(i, dc.TimeStamp - 500);
                        Logs.WriteLine(pvid + " PV_END at " + i.TimeStamp);
                    }
                }
                else
                {
                    Logs.WriteLine("DSC: ERR NO END FOUND AT " + pvid);
                    dc = _pv.Last();
                    var cd = _pv.Where(c => c.Opcode == "PV_END");
                    foreach (var i in cd)
                    {
                        pvcmds.Insert(i, dc.TimeStamp - 500);
                        Logs.WriteLine(pvid + " PV_END at " + i.TimeStamp);
                    }
                }
                _pv.Clear();
                _pv = pvcmds.Export();
                PostProcessA();
                //pvcmds.pvCommands.Clear();
                //pvcmds.Load(_pv);
                //_pv.Clear();
                //_pv = pvcmds.Export();
                SaveFile(Format.FT);
            }
            else
            {



                if (File.Exists("dsc\\common\\pv_" + pvid + "_mouth.dsc"))
                    OpenAndMerge("dsc\\common\\pv_" + String.Format("{0:000}", pvid) + "_mouth.dsc");

                if (File.Exists("dsc\\common\\pv_" + pvid + "_scene.dsc"))
                    OpenAndMergeScene("dsc\\common\\pv_" + String.Format("{0:000}", pvid) + "_scene.dsc");



                //if (File.Exists("dsc\\common\\pv_" + pvid + "_system.dsc"))
                //OpenAndMerge("dsc\\common\\pv_" + String.Format("{0:000}", pvid) + "_system.dsc");

                RemoveOpcode("SET_STAGE_EFFECT_ENV");
                //RemoveOpcode("BEAT_POINT");
                //RemoveOpcode("BAR_POINT");
                //RemoveOpcode("MOUTH_ANIM");
                RemoveOpcode("LYRIC");

                if (!String.IsNullOrEmpty(PvExpression)) SyncExpression();

                StripTime();
                //FilterNull();



                PostProcessA();
                //int timeoffset = 37500;
                //Offset("TARGET", timeoffset);
                //Offset("BAR_POINT", timeoffset);
                //Offset("BEAT_POINT", timeoffset);

                _pv.Insert(0, new PvCommand(0, "MUSIC_PLAY"));
                var co = _pv.Where(c => c.Opcode == "DATA_CAMERA");
                foreach (var k in co.ToList())
                {
                    int index = _pv.IndexOf(k);
                    var mikudispint = new int[2];
                    mikudispint[0] = 0; mikudispint[1] = 1;
                    //if (k.TimeStamp)
                    //_pv.Insert(index + 1, new PvCommand(0, "DATA_CAMERA_START", mikudispint));
                }
                co = _pv.Where(c => c.Opcode == "CHANGE_FIELD");
                foreach (var k in co.ToList())
                {
                    if (k.Parameters.Count == 2)
                        _pv.Remove(k);
                }
                co = _pv.Where(c => c.Opcode == "MOUTH_ANIM");
                foreach (var k in co.ToList())
                {
                    if (k.Parameters[2] == 14)
                    {
                        k.Parameters[2] = 28;
                        k.Parameters[3] = 100;
                        k.Parameters[4] = 1000;
                    }
                }

                var dc = _pv.Where(c => c.Opcode == "END").LastOrDefault();

                if (dc != null)
                {
                    var cd = _pv.Where(c => c.Opcode == "PV_END");
                    foreach (var i in cd)
                    {
                        i.TimeStamp = dc.TimeStamp - 500;
                        Logs.WriteLine(pvid + " PV_END at " + i.TimeStamp);
                    }
                    //int index = _pv.IndexOf(dc);
                    //_pv.Insert(index, new PvCommand(dc.TimeStamp, "PV_END"));
                }
                else
                {
                    Logs.WriteLine("DSC: ERR NO END FOUND AT " + pvid);
                    dc = _pv.Last();
                    var cd = _pv.Where(c => c.Opcode == "PV_END");
                    foreach (var i in cd)
                    {
                        i.TimeStamp = dc.TimeStamp - 500;
                        Logs.WriteLine(pvid + " PV_END at " + i.TimeStamp);
                    }
                }

                StripTime();
                OrderByTimeStamp();
                CreateTime();

                var dd = _pv.FirstOrDefault();
                if (dd.Opcode == "CHANGE_FIELD")
                    _pv.Remove(dd);
            }
            SaveFile(Format.FT);
        }

        private void ConvertFToF()
        {
            OpenFile();
            PostProcessF();
            SaveFile(Format.F);
        }

        private void ConvertFtToF()
        {
            OpenFile();
            _pv = CommandFormatting.SmartReformatFtToF(_pv, false);
            PostProcessF();
            SaveFile(Format.F);
        }

        private void ConvertFtToFt()
        {
            OpenFile();
            PostProcessFt();
            SaveFile(Format.FT);
        }

        public void ConvertToA()
        {
            switch (_fm)
            {
                case Format.DT:
                case Format.F:
                case Format.F2:
                case Format.X:
                    ConvertXToA();
                    break;
                case Format.FT:
                    ConvertFtToA();
                    break;
                default:
                    break;
            }
        }

        public void ConvertToF()
        {
            switch (_fm)
            {
                case Format.DT:
                case Format.F:
                case Format.F2:
                case Format.X:
                    ConvertFToF();
                    break;
                case Format.FT:
                    ConvertFtToF();
                    break;
                default:
                    break;
            }
        }

        public void ConvertToFt()
        {
            switch (_fm)
            {
                case Format.DT:
                case Format.F:
                case Format.F2:
                case Format.X:
                    // TODO: ConvertFToFt / Not supported yet
                    throw new Exception("Converting from F and F2nd is not supported yet.");
                case Format.FT:
                    ConvertFtToFt();
                    break;
                default:
                    break;
            }
        }
    }
}
